# $Id: db4.py 9227e81e919a 2012/08/27 15:49:57 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

__version__  = "$Revision: 9227e81e919a $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2012-08-27 15:49:57Z $"

import bsddb

# What types of database does this module support
DB_HASH = bsddb.db.DB_HASH
DB_BTREE = bsddb.db.DB_BTREE
DB_RECNO = bsddb.db.DB_RECNO

from exception import DBError

# Database class, more similar to dictionary
class bdb(object):
   # constructor, takes the following
   #  1. Database - name of the file
   #  2. Type - Default is hash
   #  3. Readonly, whether you want to only read the database
   def __init__(self, DataBase, Type = bsddb.db.DB_HASH, ReadOnly = False):
      # Filename of the database
      self._dbFile = None

      # Database type
      self._type = None

      # Is the database readonly?
      self._dbReadOnly = False

      # See whether the database descriptor is open or not
      self._dbOpen = False

      # Instantiate the DB
      self._db = bsddb.db.DB()

      if DataBase:
         self._dbFile = DataBase
      else:
         self._error("database file not specified!")

      if Type:
         self._type = Type

      # Open the database, based on the passed parameters to the constructor
      try:
         if ReadOnly:
            self._db.open(self._dbFile, None, self._type, bsddb.db.DB_RDONLY)
            self._dbReadOnly = True
         else:
            self._db.open(self._dbFile, None, self._type, bsddb.db.DB_CREATE)
      except bsddb.db.DBFileExistsError as err:
         self._error(err)
      else:
         self._dbOpen = True

   def __doc__(self):
      retval = "Berkely database object"

      return retval

   def __version__(self):
      return bsddb.db.DB_VERSION_STRING

   # Close the database instance
   def close(self):
      if self._dbOpen:
         try:
            self._db.close()
         except bsddb.db.DBError as err:
            raise DBError(err)

         self._dbOpen = False

   def keys(self):
      retval = []
      if self._dbOpen:
         cursor = self._db.cursor()
         recv = cursor.first()
         while recv:
            retval.append(recv[0])
            recv = cursor.next()
      else:
         self._error("no database defined")

      return retval

   # Error handling function, closing the db instance and raises error,
   # default error class is DBError
   def _error(self, Message, Error = DBError):
      self.close()
      if Message:
         raise Error(Message)

   def __nonzero__(self):
      return self._dbOpen

   def __str__(self):
      return self._dbFile.encode('utf-8')

   def __repr__(self):
      return repr(str(self))

   def __unicode__(self):
      return unicode(str(self))

   def __len__(self):
      count = 0
      if self._dbOpen:
         cursor = self._db.cursor()
         recv = cursor.first()
         while recv:
            count += 1
            recv = cursor.next()

      return count

   # Set item, example:
   # temp = db4.bdb(tempfile)
   # temp["foo"] = "bar"
   def __setitem__(self, Key, Value):
      if not isinstance(Value, str):
         self._error("only string values are supported!")

      if self._dbReadOnly:
         self._error("write operation on a readonly database", TypeError)

      if not self._dbOpen:
         self._error("no database defined!")

      if Key and Value:
         try:
            self._db.put(Key, Value)
         except TypeError as err:
            self._error(err)
      else:
         self._error("no key/value pair", KeyError)

   # For for loops
   # for (keys, values) in db:
   #     print keys
   def __iter__(self):
      if self._dbOpen:
         cursor = self._db.cursor()
         recv = cursor.first()
         while recv:
            yield(recv[0], recv[1])
            recv = cursor.next()
      else:
         self._error("no database defined")

   # For "in" operator
   def __contains__(self, Key):
      retval = False
      temp = None
      try:
         temp = self[Key]
      except DBError:
         pass

      if temp:
         retval = True

      return retval

   # For setting the value
   def __getitem__(self, Key):
      retval = None
      if self._dbOpen:
         flag = True
         cursor = self._db.cursor()
         recv = cursor.first()
         while recv and flag:
            if recv[0] == Key:
               retval = recv[1]
               flag = False
            else:
               recv = cursor.next()
      else:
         raise KeyError, "%s not defined!"

      return retval

   def __delitem__(self, Key):
      if self[Key]:
         self._db.delete(Key)

   @property
   def stat(self):
      retval = {}
      if self._dbOpen:
         retval = self._db.stat()

      return retval
